package client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;

import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import messages.Message;
import messages.encrypted.EncryptedMessage;
import messages.plain.PlainAuthRequest;
import messages.plain.PlainAuthResponse;
import messages.plain.PlainAuthResponseAck;
import messages.plain.TextMessage;
import utils.ClientAuthState;
import utils.ConstantVariables;

/**
 * Top wrapper class for clients.
 * 
 * @author bh349
 */
public abstract class Client
{
    protected PublicKey dstPublicKey;

    protected PublicKey publicKey;

    protected PrivateKey privateKey;

    protected Socket socket;

    protected ServerSocket serverSocket;

    protected ClientAuthState authState;

    protected ObjectOutputStream out;

    protected int nonce;

    protected SecureRandom secureRandom;

    public Client() throws InvalidKeySpecException, NoSuchAlgorithmException,
            IOException {
        secureRandom = new SecureRandom();
        privateKey = getPrivateKeyFromFile();
        authState = ClientAuthState.NO_AUTH;
        try {
            socket = new Socket( ConstantVariables.IP,
                    ConstantVariables.MALLORY_PORT );
            out = new ObjectOutputStream( socket.getOutputStream() );
        } catch( IOException e ) {
            e.printStackTrace();
        }
        System.out.println( "Connection established (Mallory is listening)" );
    }

    protected void createMsgs( Client client ) throws IOException,
            InvalidKeyException, NoSuchAlgorithmException,
            NoSuchPaddingException, IllegalBlockSizeException {
        String s;
        System.out.println( ConstantVariables.CLIENT_INSTRUCTION );
        BufferedReader br = new BufferedReader( new InputStreamReader(
                System.in ) );
        while( (s = br.readLine()) != null ) {
            char messageNumber = s.charAt( 0 );
            char isEncrypted = s.charAt( 1 );
            Message msg = null;

            switch (messageNumber) {
            case '1': {
                nonce = secureRandom.nextInt();
                if( client instanceof Alice )
                    msg = new PlainAuthRequest( "Alice", nonce );
                else
                    msg = new PlainAuthRequest( "Bob", nonce );
                authState = ClientAuthState.AUTHREQ_SENT;
                break;
            }
            case '2': {
                nonce = secureRandom.nextInt();
                System.out.println( "Enter target's nonce" );
                s = br.readLine();
                int dstNonce = Integer.parseInt( s );
                if( client instanceof Alice )
                    msg = new PlainAuthResponse( "Alice", dstNonce, nonce );
                else
                    msg = new PlainAuthResponse( "Bob", dstNonce, nonce );
                authState = ClientAuthState.AUTHRESP_SENT;
                break;
            }
            case '3': {
                System.out.println( "Enter target's nonce" );
                s = br.readLine();
                int dstNonce = Integer.parseInt( s );
                msg = new PlainAuthResponseAck( dstNonce, dstPublicKey );
                break;
            }
            case '4': {
                System.out.println( "Enter a line of msg" );
                s = br.readLine();
                msg = new TextMessage( s );
                break;
            }
            default:
                System.out.println( "Error input." );
                continue;
            }
            if( isEncrypted == '2' ) {
                msg = new EncryptedMessage( msg, dstPublicKey );
            }
            sendMessage( msg );
            System.out.println( "Message Sent.\n"
                    + ConstantVariables.CLIENT_INSTRUCTION );
        }
    }

    /**
     * client sends out a message.
     * 
     * @param msg
     * @throws IOException
     */
    protected void sendMessage( Message msg ) throws IOException {
        if( socket != null ) {
            out.writeObject( msg );
            // out.flush();
        }
        System.out.println( "Current Auth State:" + authState );
    }

    /**
     * Read private key of the client itself.
     * 
     * @return
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    protected abstract PrivateKey getPrivateKeyFromFile() throws IOException,
            InvalidKeySpecException, NoSuchAlgorithmException;
}
